/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
  <file> UlsLex.h </file>
  <brief>
 	The wrapper class of ulslex_t, the main structure of ULS c-library, libuls.*
    This file is part of ULS, Unified Lexical Scheme.
  </brief>
  <author>
    Stanley J. Hong <babel96@gmail.com>, 2011.
  </author>
*/

#include "uls.h"
#include <stdarg.h>
#include <iostream>

#ifndef __ULSCPP_LEX_H__
#define __ULSCPP_LEX_H__

namespace ULS {
	typedef uls_mutex_t MutexType;

	// <brief>
	// Initializes the parameter 'mtx' and the 'mtx' can be used, locked/unlocked.
	// </brief>
	// <parm name="mtx">A mutex object used in ULS</parm>
	// <return>none</return>
	void initMutex(MutexType mtx);

	// <brief>
	// De-initialize the 'mtx'.
	// Hereafter , you must not use 'mtx'.
	// </brief>
	// <parm name="mtx">A mutex object used in ULS</parm>
	// <return>none</return>
	void deinitMutex(MutexType mtx);

	// <brief>
	// The lock API in ULS.
	// </brief>
	// <parm name="mtx">A mutex object used in ULS</parm>
	// <return>none</return>
	void lockMutex(MutexType mtx);

	// <brief>
	// The unlock API in ULS.
	// </brief>
	// <parm name="mtx">A mutex object used in ULS</parm>
	// <return>none</return>
	void unlockMutex(MutexType mtx);

	// <brief>
	// Lists the directories in which ULS-object in order searches for ulc-file(*.ulc)
	// that would be specified in uls_create('confname', ...).
	// </brief>
	// <parm name="confname">The name of lexcial configuration, as like 'sample' or 'sample.ulc'</parm>
	// <return>none</return>
	void dumpSearchPathOfUlc(std::string& confname);

	// <brief>
	// The main class of ulscpp library.
	// You may instantiate this class with a lexical configuration(*.ulc)
	// and use the object for lexical analysis.
	// </brief>
	class UlsLex {
		uls_lex_t uls;
		uls_lf_t  uls_lf;

		DECLARE_ULS_MUTEX(syserr_g_mtx);
		void *syslog;
		uls_lf_puts_t syslog_puts;
		uls_lf_csz_t mb_lbuf;

		DECLARE_ULS_MUTEX(sysprn_g_mtx);
		int sysprn_opened;
		uls_lf_csz_t mb_buf;
		void *sysprn;
		uls_lf_puts_t sysprn_puts;

		// <brief>
		// Prints the wide string formatted by variable arguments in the varargs(stdarg) style.
		// </brief>
		// <parm name="wfmt">The format string, a template for printing</parm>
		// <parm name="args">The list of args</parm>
		// <parm name="p_wlen">a out-parameter, the length of returning string, # of wchar_t's</parm>
		// <return>The formatted wide string</return>
		uls_lf_word_t* vwprint(const uls_lf_word_t* wfmt, va_list args, int* p_wlen);

		static const int N_CHARS_LOGBUF = 128;

	protected:
		// <brief>
		// Emits a message after formatting the string 'fmt' with 'args'
		// </brief>
		// <parm name="fmt">The format string, a template for printing</parm>
		// <parm name="args">The list of args</parm>
		// <return>none</return>
		void err_vlog(const uls_lf_char_t* fmt, va_list args);

		// <brief>
		// A method that emits a message after formatting the string 'fmt' with 'args'
		// </brief>
		// <parm name="fmt">The format string, a template for printing</parm>
		// <parm name="args">The list of args</parm>
		// <return>none</return>
		void vlog(const char* fmt, va_list args);

		// <brief>
		// A method that emits a wide string message after formatting the string 'wfmt' with 'args'
		// </brief>
		// <parm name="wfmt">The format string, a template for printing</parm>
		// <parm name="args">The list of args</parm>
		// <return>none</return>
		void vwlog(const wchar_t* wfmt, va_list args);

		// <brief>
		// Stores a wide string formatted by the string 'wfmt' with 'args' to 'wbuf'.
		// </brief>
		// <parm name="wbuf">The buffer of wchar_t to store the result.</parm>
		// <parm name="wbuf_siz">The size of 'wbuf', # of wchar_t's</parm>
		// <parm name="wfmt">The format string, a template for printing</parm>
		// <parm name="args">The list of args</parm>
		// <return># of wchar_t's int 'wbuf'</return>
		int snvwprintf(uls_lf_word_t* wbuf, int wbuf_siz, const uls_lf_word_t *wfmt, va_list args);
	
		// <brief>
		// Stores a 'char' string formatted by the string 'fmt' with 'args' to 'buf'.
		// </brief>
		// <parm name="buf">The buffer of char to store the result.</parm>
		// <parm name="fmt">The format string, a template for printing</parm>
		// <parm name="args">The list of args</parm>
		// <return># of chars in 'buf'</return>
		int vsprintf(uls_lf_char_t *buf, const uls_lf_char_t *fmt, va_list args);

		// <brief>
		// Skips the white chars irregardless of token.
		// </brief>
		// <return>none</return>
		void skipWhiteSpace(void);

		// <brief>
		// Peeks the next char in the internal buffer.
		// Returns the corresponding information with the literal string in case that the next char is a quotation char.
		// </brief>
		// <parm name="pp_quotetype">out-param, The information of the literal string</parm>
		// <return>The char which this object indicates to as the next char.</return>
		char peekChInternal(istr_quotetype_t** pp_quotetype);

	public:

		// <brief>
		// A constructor that creates a objec for lexical analysis.
		// </brief>
		// <parm name="ulc_file">The name/path of the lexical configuration.</parm>
		// <return>none</return>
		UlsLex(std::string& ulc_file);

		// <brief>
		// The destuctor of UlsLex.
		// </brief>
		// <return>none</return>
		virtual ~UlsLex();

		// <brief>
		// The class 'UlsLex' is just wrapper class of C-struct 'uls_lex_t'.
		// This returns the pointer of the C-struct.
		// </brief>
		// <return>The pointer of 'uls_lex_t'</return>
		uls_lex_t* GetLexCore(void) { return &uls; }

		// <brief>
		// The current token id, a integer value.
		// This field is updated by each call methods 'getTok()' or 'next()'.
		// </brief>
		int TokenId;

		// <brief>
		// The current lexeme of token id, a string value.
		// This field is updated by each call methods 'getTok()' or 'next()'.
		// </brief>
		std::string Lexeme;

		// <brief>
		// The current value of lexeme.
		// If the current token is of type integer, the field 'LexemeInt' has valid value.
		// If the current token is of type float/double, the field 'LexemeDouble' has valid value.
		// This field is updated by each call methods 'getTok()' or 'next()'.
		// </brief>
		int LexemeInt;
		double LexemeDouble;

		// <brief>
		// Checks if the position in input stream is the end of input or not.
		// </brief>
		bool isEOI;

		// <brief>
		// Checks if the position in input stream is the end of file or not.
		// There may be a lots of files in a streaming session.
		// A streaming seesion is ended with EOI, end of input.
		// </brief>
		bool isEOF;

		// <brief>
		// Checks if the current token is of type ID, TOK_ID.
		// A ID in ULS, by default, consists of an alphabetical char and subsequent alpha-numerical ones.
		// </brief>
		bool isIdentifer;

		// <brief>
		// Checks if the current token is of type number, TOK_NUMBER.
		// A number in ULS is a integer or an real represented by the exponential expression.
		// </brief>
		bool isNumber;

		// <brief>
		// This field is true if the current token is number and integer, otherwise false.
		// </brief>
		bool isInteger;

		// <brief>
		// This field is true if the current token is number and real, otherwise false.
		// </brief>
		bool isReal;

		// <brief>
		// This attribute represents the location of the current token with 'FileName' below.
		// </brief>
		int LineNum;

		// <brief>
		// Sets the 'LineNum' forcibly or intially.
		// </brief>
		// <parm name="lineno">The new value of 'LineNum' to be updated</parm>
		// <return>none</return>
		void setLineNum(int lineno);

		// <brief>
		// Adds some lines to the current line number to update the current LineNum forcibly.
		// </brief>
		// <parm name="amount">The amount of lines to be added</parm>
		// <return>none</return>
		void addLineNum(int amount);

		// <brief>
		// This attribute represents the location of the current token with 'LineNum' above.
		// </brief>
		std::string FileName;

		// <brief>
		// This sets the 'FileName' as the parameter 'fname'
		// </brief>
		// <parm name="fname">The new string value of 'FileName' to be updated</parm>
		// <return>none</return>
		void setFileName(std::string& fname);

		// <brief>
		// The log level of this object as it processes input files.
		// </brief>
		// <parm name="lvl">new value of log level</parm>
		// <return>none</return>
		void setLogLevel(int lvl);

		// <brief>
		// Clears the log levels defined in this class.
		// </brief>
		// <parm name="lvl">the log level to be cleared.</parm>
		// <return>void</return>
		void clearLogLevel(int lvl);

		// <brief>
		// Checks if the 'lvl' is set in the internally.
		// </brief>
		// <parm name="lvl">the log level to be checked.</parm>
		// <return>true/false</return>
		bool isLogLevelSet(int lvl);

		// <brief>
		// Changes the keyword compare-interface internally defined.
		// </brief>
		// <parm name="proc">A new interface.</parm>
		// <return>none</return>
		void changeStrcmp(uls_strcmp_t proc);

		// <brief>
		// Delete the literal string analyzing interface starting with 'pfx'
		// </brief>
		// <parm name="pfx">Finds the literal string analyzer.</parm>
		// <return>none</return>
		void deleteLiteralAnalyzer(std::string& pfx);

		// <brief>
		// Changes the literal string analyzer to 'proc'.
		// The 'proc' will be applied to the literal string starting with 'pfx'.
		// </brief>
		// <parm name="pfx">The prefix of literal string that will be processed by 'proc'</parm>
		// <return>void</return>
		void changeLiteralAnalyzer(std::string& pfx, istr_analyzer_t proc);

		// <brief>
		// Sets the input file to be tokenized by giving its file descriptor 'fd'
		// </brief>
		// <parm name="fd">the file descriptor of input file.</parm>
		// <parm name="fd_type">The type of 'fd'. The type of input file can be source(RAW) or ULS-file(*.uls).</parm>
		// <parm name="tag">'tag' will be the field 'FileName' in this class.</parm>
		// <parm name="flags">Set ULS_WANT_EOF_TOK if you want TOK_EOF whenever each file reaches at the end of file</parm>
		// <return>none</return>
		void setFdType(int fd, int fd_type, std::string& tag, int flags);

		// <brief>
		// This method will push the input-source specified by fd on the internal stack.
		// The current input-source will be resumed at the end of 'fd' from the postion where this called.
		// </brief>
		// <parm name="fd">the file descriptor of input file.</parm>
		// <parm name="fd_type">The type of 'fd'. The type of input file can be source(RAW) or ULS-file(*.uls).</parm>
		// <parm name="tag">'tag' will be the field 'FileName' in this class.</parm>
		// <parm name="flags">Set ULS_WANT_EOF_TOK if you want TOK_EOF whenever each file reaches at the end of file</parm>
		// <return></return>
		void pushFdType(int fd, int fd_type, std::string& tag, int flags);

		// <brief>
		// This method does the same things as pushFdType() except this accepts the parameter hdr, the header info of uls-file.
		// There are the attributes that pushFdType() needs to know.
		// </brief>
		// <parm name="fd">the file descriptor of input file.</parm>
		// <parm name="hdr">the header information of uls-file(*.uls)</parm>
		// <parm name="flags">Set ULS_WANT_EOF_TOK if you want TOK_EOF whenever each file reaches at the end of file</parm>
		// <return>void</return>
		void pushFdTypeWithHeader(int fd, uls_stream_t& hdr, int flags);

		// <brief>
		// This method pushes (raw) source-file on the internal stack.
		// </brief>
		// <parm name="fd">the file descriptor of input file.</parm>
		// <parm name="flags">Set ULS_WANT_EOF_TOK if you want TOK_EOF whenever each file reaches at the end of file</parm>
		// <return>void</return>
		void pushFd(int fd, int flags);

		// <brief>
		// This overwrites or calls off the state of the current input-source and
		// sets the new file of 'fd' to the input-source to be processed.
		// </brief>
		// <parm name="fd">the file descriptor of input file.</parm>
		// <parm name="flags">Set ULS_WANT_EOF_TOK if you want TOK_EOF whenever each file reaches at the end of file</parm>
		// <return>none</return>
		void setFd(int fd, int flags);

		// <brief>
		// This method will push the input-file with its header on the internal stack.
		// The 'hdr' may be of RAW or of ULS(*.uls).
		// </brief>
		// <parm name="hdr">the header information of uls-file(*.uls)</parm>
		// <parm name="flags">Set ULS_WANT_EOF_TOK if you want TOK_EOF whenever each file reaches at the end of file</parm>
		// <return>void</return>
		void pushFileWithHeader(uls_stream_t& hdr, int flags);

		// <brief>
		// This method will push the input-file with its file path on the internal stack of input-sources.
		// </brief>
		// <parm name="filepath">The file path of input.</parm>
		// <parm name="flags">Set ULS_WANT_EOF_TOK if you want TOK_EOF whenever each file reaches at the end of file</parm>
		// <return></return>
		void pushFile(std::string& filepath, int flags);

		// <brief>
		// This method will overwrite the current input-source on the top of stack to new one with its header.
		// </brief>
		// <parm name="hdr">the header information of uls-file(*.uls)</parm>
		// <parm name="flags">Set ULS_WANT_EOF_TOK if you want TOK_EOF whenever each file reaches at the end of file</parm>
		// <return>none</return>
		void setFileWithHeader(uls_stream_t& hdr, int flags);

		// <brief>
		// This method will overwrite the current input-source on the top of stack to new one with its file path.
		// </brief>
		// <parm name="filepath">The file path of input.</parm>
		// <parm name="flags">Set ULS_WANT_EOF_TOK if you want TOK_EOF whenever each file reaches at the end of file</parm>
		// <return>none</return>
		void setFile(std::string& filepath, int flags);

		// <brief>
		// This method will push the literal string as an input-source on the top of the stack.
		// </brief>
		// <parm name="line">A input source as an literal string</parm>
		// <parm name="len">The length of 'line'</parm>
		// <parm name="flags">You can set the flag ULS_WANT_EOF_TOK.</parm>
		// <return>none</return>
		void pushLine(char* line, int len, int flags);

		// <brief>
		// This method will overwrite the input-source on the top of the stack into 'line'.
		// </brief>
		// <parm name="line">A input source as an literal string</parm>
		// <parm name="len">The length of 'line'</parm>
		// <parm name="flags">You can set the flag ULS_WANT_EOF_TOK.</parm>
		// <return>none</return>
		void setLine(char* line, int len, int flags);

		// <brief>
		// Peeks what is the next character in the internal buffer.
		// </brief>
		// <return>The next character</return>
		char peekCh(void);

		// <brief>
		// Extracts the next character.
		// </brief>
		// <return>The next character</return>
		char getCh(void);

		// <brief>
		// Cancel the call of 'getCh()'
		// </brief>
		// <return>none</return>
		void ungetCh(void);

		// <brief>
		// Advances the cursor of the lexical object to the next step.
		// Returns an integer representing token id.
		// </brief>
		// <return></return>
		int getToken(void);

		// <brief>
		// Same as getToken().
		// </brief>
		// <return>The current token number.</return>
		int next(void);

		// <brief>
		// If the current token-id is not TokExpected, An Exception will be thrown.
		// </brief>
		// <parm name="TokExpected">I expect that the token be same as 'TokExpected'</parm>
		// <return></return>
		void expect(int TokExpected);

		// <brief>
		// An arbitrary token 'sym_no' will be the next token after this method is called.
		// </brief>
		// <parm name="sym_no">A token number</parm>
		// <parm name="dat">The lexeme associated with 'sym_no'</parm>
		// <parm name="dat_len">The length of 'dat'</parm>
		// <return>none</return>
		void pushback(int sym_no, void* dat, int dat_len);

		// <brief>
		// Call this method if you want get the current token again after getTok().
		// </brief>
		// <return>none</return>
		void ungettok(void);

		// <brief>
		// Prints the information on the current token.
		// </brief>
		// <return>none</return>
		void printTok(void);

		// <brief>
		// Return the string to which the token number is converted.
		// </brief>
		// <parm name="t">A token number</parm>
		// <return>A string</return>
		std::string getKeywordStr(int t);

		// <brief>
		// Logs a formatted message if the 'loglvl' is set in the object.
		// </brief>
		// <parm name="loglvl">This message will be printed if 'loglvl' is set.</parm>
		// <return>none</return>
		void log(int loglvl, const char* fmt, ...);

		// <brief>
		// Logs a formatted message
		// You can use %k %w to print the current token, its location.
		// No need to append '\n' to the end of line 'fmt'
		// </brief>
		// <parm name="fmt">The template for message string</parm>
		// <return>void</return>
		void log(const char* fmt, ...);

		// <brief>
		// Logs a formatted message and the program will die.
		// No need to append '\n' to the end of line 'fmt'
		// </brief>
		// <parm name="fmt">The template for message string</parm>
		// <return>none</return>
		void panic(const char* fmt, ...);

		// <brief>
		// An wide string version of the method 'log()'
		// </brief>
		// <parm name="loglvl">This message will be printed if 'loglvl' is set.</parm>
		// <parm name="wfmt">The template for message string</parm>
		// <return>none</return>
		void wlog(int loglvl, const wchar_t* wfmt, ...);

		// <brief>
		// An wide string version of the method 'log()'
		// </brief>
		// <parm name="wfmt">The template for message string</parm>
		// <return>none</return>
		void wlog(const wchar_t* wfmt, ...);

		// <brief>
		// An wide string version of the method 'panic()'
		// </brief>
		// <parm name="wfmt">The template for message string</parm>
		// <return>none</return>
		void wpanic(const wchar_t* wfmt, ...);

		// <brief>
		// Opens a file for output.
		// After calls of print() or wprint(), be sure to call closeOutput().
		// </brief>
		// <parm name="out_file">The output file path</parm>
		// <return>none</return>
		void openOutput(std::string& out_file);

		// <brief>
		// closes the output file used by print() or wprint().
		// </brief>
		// <return>none</return>
		void closeOutput(void);

		// <brief>
		// This will print the formatted string to the standard oupput, by default.
		// But you can change the output port by calling 
		// </brief>
		// <parm name="fmt">The template for message string</parm>
		// <return># of chars printed</return>
		int print(const uls_lf_char_t* fmt, ...);

		// <brief>
		// An wide string version of the method 'print()'
		// </brief>
		// <parm name="wfmt">The template for message string</parm>
		// <return># of wchar_t's</return>
		int wprint(const uls_lf_word_t* wfmt, ...);

		// <brief>
		// This stores the formatted string to the 'buf'.
		// </brief>
		// <parm name="buf">The output buffer for the formatted string</parm>
		// <parm name="bufsiz">The size of 'buf'</parm>
		// <parm name="fmt">The template for message string</parm>
		// <return># of chars filled except for '\0'</return>
		int snprintf(uls_lf_char_t* buf, int bufsiz, const uls_lf_char_t *fmt, ...);

		// <brief>
		// An wide string version of the method 'snprintf()'
		// </brief>
		// <parm name="wbuf">The output buffer for the formatted string</parm>
		// <parm name="wbufsiz">The size of 'buf', # of wchar_t's</parm>
		// <parm name="wfmt">The template for message string</parm>
		// <return># of wchar_t's</return>
		int snwprintf(uls_lf_word_t* wbuf, int wbuf_siz, const uls_lf_word_t *wfmt, ...);

		// <brief>
		// This stores the formatted string to the 'buf' with its size unknown.
		// I recommend not to use this but snprintf().
		// </brief>
		// <parm name="buf">The output buffer for the formatted string</parm>
		// <parm name="fmt">The template for message string</parm>
		// <return># of chars filled</return>
		int sprintf(uls_lf_char_t * buf, const uls_lf_char_t *fmt, ...);

		// <brief>
		// An wide string version of the method 'sprintf()'
		// </brief>
		// <parm name="wbuf">The output buffer for the formatted string</parm>
		// <parm name="wfmt">The template for message string</parm>
		// <return></return>
		int swprintf(uls_lf_word_t * wbuf, const uls_lf_word_t *wfmt, ...);

		// <brief>
		// This stores the formatted string to the 'fp', FILE*.
		// </brief>
		// <parm name="fp">The pointer of FILE to be wrriten</parm>
		// <parm name="fmt">The template for message string</parm>
		// <return># of chars written</return>
		int fprintf(FILE* fp, const uls_lf_char_t *fmt, ...);

		// <brief>
		// An wide string version of the method 'fprintf()'
		// </brief>
		// <parm name="fp">The pointer of FILE to be wrriten</parm>
		// <parm name="wfmt">The template for message string</parm>
		// <return># of wchar_t's written</return>
		int fwprintf(FILE* fp, const uls_lf_word_t *wfmt, ...);

		// <brief>
		// This prints the formatted string to the 'stdout'.
		// </brief>
		// <parm name="fmt">The template for message string</parm>
		// <return># of chars printed</return>
		int printf(const uls_lf_char_t *fmt, ...);

		// <brief>
		// An wide string version of the method 'printf()'
		// </brief>
		// <parm name="wfmt">The template for message string</parm>
		// <return># of chars printed</return>
		int wprintf(const uls_lf_word_t *wfmt, ...);

		// <brief>
		// Changes the associated procedure with 'percent_name', a converion specification.
		// The procedure will output string with puts_proc which can be set by 'changePuts'
		// </brief>
		// <parm name="percent_name">A converion specification without the percent character itself.</parm>
		// <parm name="proc">The user defined procedure for processing '%percent_name'</parm>
		// <return>none</return>
		void changeConvSpec(uls_lf_char_t* percent_name, uls_lf_convspec_t proc);

		// <brief>
		// You can use this method to change the default output interface for logging.
		// </brief>
		// <parm name="puts_proc">A newly output interface</parm>
		// <return>none</return>
		void changePuts(uls_lf_puts_t puts_proc);
	};
}

#endif // __ULSCPP_LEX_H__

